package autodoc

import (
	"errors"
	"fmt"
	"io/ioutil"
	"reflect"
	"regexp"
	"sort"
	"strconv"
	"strings"
)

const (
	template=`
####%s

请求地址：%s

请求方式：%s

####请求参数
| 参数   | 类型  | 必填 | 说明 |
| :---:   | :---: | :---: | :---: |
%s


%s

####响应参数
%s

`

	structTagField="field"

)

//自动化文档
type AutoDoc struct {
	open bool //自动文档开关
	url string// 请求地址
	titleMap map[string/*url*/]string/*方法注释*/ //url标题map

	method string //请求方式

	file string //文档文件地址

	requiredFields []string //必填字段

	requestCareField []string //非必填字段,为空，表示所有结构体字段都是非必填字段
	requestParams []RequestParam //存储请求数据


	requestRemark string //请求备注数据

	responseString string //生成的响应数据



	controllerDirFiles []string //控制器下文件

	controllerDir string //控制器文件路径



}

//请求参数
type RequestParam struct {
	Field     string //字段
	FieldType string //字段类型
	Required  bool   //必填字段
	Desc      string //字段说明
}

func NewAutoDoc(controllerDir string)  *AutoDoc{
	this:=&AutoDoc{
		titleMap:make(map[string]string),
		controllerDir:controllerDir,
	}

	this.loadControllerFiles().setTitleMap()
	return this
}

//设置文档文件地址
func (this *AutoDoc)getFile() (file string,err error) {
	urlArr:=strings.Split(this.url,"/")
	if len(urlArr)== 0 {
		return "",errors.New("未设置文档请求地址")
	}

	return this.file+fmt.Sprintf("%s.md",urlArr[0]),nil
}

//设置请求地址
func (this *AutoDoc)SetUrl(url string) *AutoDoc {
	this.url=url
	return this
}

//设置请求方式
func (this *AutoDoc)SetMethod(method string) *AutoDoc  {

	this.method=method
	return this
}

func (this *AutoDoc)SetRequestCareField(fields ...string) *AutoDoc  {
	this.requestCareField=append(this.requestCareField,fields...)
	return this
}

func (this *AutoDoc)setRequestRecursive(t reflect.Type)  {
	var (

		requestParams []RequestParam
	)

	for i:=0;i<t.NumField();i++{
		field:=t.Field(i)

		//不处理
		if field.Tag.Get("json")== "-" {
			continue
		}

		if len(this.requestCareField)!= 0 {
			if !inArray(field.Name,append(this.requestCareField,this.requiredFields...)) {
				continue
			}
		}

		fieldType:=field.Type.String()

		if strings.Contains(fieldType, ".") {
			fieldTypeArr:=strings.Split(fieldType,".")
			if strings.Contains(fieldType, "[]") {
				fieldType="[]"+fieldTypeArr[len(fieldTypeArr)-1]
			}else{
				fieldType=fieldTypeArr[len(fieldTypeArr)-1]
			}
		}



		item:= RequestParam{
			Field:field.Name,
			FieldType:fieldType,
			Required:inArray(field.Name,this.requiredFields),
			Desc:field.Tag.Get(structTagField),
		}

		if item.Field==item.FieldType  {
			this.setRequestRecursive(field.Type)
			continue
		}

		switch field.Type.Kind() {
		case reflect.Struct:
			this.setRequestRemark(field.Type)
		case reflect.Slice,reflect.Ptr:
			this.setRequestRemark(field.Type.Elem())
		}


		if item.Desc== "" {
			item.Desc=item.Field
		}


		requestParams=append(requestParams,item)
	}

	this.SetRequestParam(requestParams)

}

//设置请求参数
func (this *AutoDoc)SetRequest(param interface{},requiredFields ...string) *AutoDoc {
	this.requiredFields=append(this.requiredFields,requiredFields...)
	var (
		t reflect.Type

	)

	t,_,_=getStructTV(param)
	this.setRequestRecursive(t)

	return this
}

//设置备注
func (this *AutoDoc)setRequestRemark(t reflect.Type)  {

	if t.Kind()==reflect.Slice {
		t=t.Elem()
	}

	if t.Kind()!=reflect.Struct {
		return
	}

	this.requestRemark+=fmt.Sprintf("%s:{\n",t.Name())
	for i:=0;i<t.NumField();i++{
		field:=t.Field(i)
		if field.Tag.Get("json")== "-" {
			continue
		}

		tag:=field.Tag.Get(structTagField)
		if tag!= "" {
			tag="//"+tag
		}
		this.requestRemark+=fmt.Sprintf("    %s %s %s\n",field.Name,field.Type.String(),tag)
	}

	this.requestRemark+="}\n"


}

//设置参数数据
func (this *AutoDoc) SetRequestParam(requestParams []RequestParam) *AutoDoc {
	this.requestParams=append(this.requestParams,requestParams...)


	return this
}
func (this *AutoDoc)getRequestParamString() (requestString string) {
	boolConvert:= func(a bool) int{
		if a {
			return 1
		}

		return 0

	}

	//必填顺序调整
	sort.SliceStable(this.requestParams, func(i, j int) bool {
		return boolConvert(this.requestParams[i].Required)>boolConvert(this.requestParams[j].Required)
	})

	for _,v:=range this.requestParams {
		required:=""
		if v.Required {
			required="是"
		}
		requestString+=fmt.Sprintf("| %s | %s | %s |  %s |\n",v.Field,v.FieldType,required,v.Desc)
	}

	return
}

//设置响应参数
func (this *AutoDoc)SetResponse(param interface{}) *AutoDoc {
	if param== nil {
		return this
	}
	t,_,_:=getStructTV(param)
	this.responseString=this.responseStringRecursive(t,"","    ",false)
	return this
}


//递归生成输出参数
func (this *AutoDoc)responseStringRecursive(t reflect.Type,name string,space string,embed bool) (s string) {


	if !embed{

		if t.Kind()==reflect.Slice {

			if name!= "" {
				name+=":"
			}

			name+="[]"

			t=t.Elem()
		}else{
			if name!= "" {
				name+=":"
			}

		}

		s=fmt.Sprintf("%s%s{\n",space,name)
	}



	if t.Kind()==reflect.Ptr {
		t=t.Elem()
	}

	if t.Kind()!=reflect.Struct {
		return ""
	}


	for i:=0;i<t.NumField();i++{
		field:=t.Field(i)

		if field.Tag.Get("json")== "-" {
			continue
		}

		switch field.Type.Kind() {
		case reflect.Ptr:
			fieldType:=field.Type.String()

			if strings.Contains(fieldType, ".") {
				fieldTypeArr:=strings.Split(fieldType,".")
				if strings.Contains(fieldType, "[]") {
					fieldType="[]"+fieldTypeArr[len(fieldTypeArr)-1]
				}else{
					fieldType=fieldTypeArr[len(fieldTypeArr)-1]
				}
			}

			if fieldType==field.Name {
				//嵌入的结构体
				s+=this.responseStringRecursive(field.Type,"",space,true)

			}else{
				s+=this.responseStringRecursive(field.Type,field.Name,"    "+space,false)
			}

		case reflect.Slice,reflect.Struct,reflect.Interface:
			s+=this.responseStringRecursive(field.Type,field.Name,"    "+space,false)

		case reflect.String,reflect.Int,reflect.Int8,reflect.Int16,reflect.Int32,reflect.Int64,reflect.Uint8,reflect.Uint16,reflect.Uint32,reflect.Uint64,reflect.Float32,reflect.Float64:


			tag:=field.Tag.Get(structTagField)
			if tag!= "" {
				tag="//"+tag
			}
			s+=fmt.Sprintf("%s%s %s %s\n","    "+space,field.Name,field.Type.String(),tag)
		}


	}

	if !embed {
		s+=space+"}\n"
	}


	return s

}




//自动生成文档
func (this *AutoDoc)MakeAutoFile() (err error) {
	var (

		file string
		fileContent []byte
		newContent string
	)



	if file,err=this.getFile();err!= nil {
		return
	}

	fileContent,_=ioutil.ReadFile(file);
	//已经产生过地址，不再生成
	if strings.Contains(string(fileContent),this.url) {
		return
	}

	newContent+=string(fileContent)+this.make()

	return ioutil.WriteFile(file,[]byte(newContent),0664)
}

//临时输出
func (this *AutoDoc)Create()  {
	err:=ioutil.WriteFile("./autodoc.md",[]byte(this.make()),0664)
	if err != nil {
		fmt.Println("AutoDoc.Create 错误：",err)
	}
}

func (this *AutoDoc)make()(newContent string)  {
	var (
		remark string
	)

	if this.requestRemark!= "" {
		remark=fmt.Sprintf("####备注```\n%s```\n",this.requestRemark)
	}

	this.responseString=strings.Trim(this.responseString," ")
	this.responseString=strings.Trim(this.responseString,"\n")
	responseString:=fmt.Sprintf("```\n"+`{
    "Code": 0,
    "Data":%s,
    "Msg": ""
}
`+"```\n",this.responseString)
	newContent+="---\n"
	newContent+=fmt.Sprintf(template,this.getTitle(),this.url,this.method,this.getRequestParamString(),remark,responseString)
	newContent+="---\n"

	return
}

//设置url和title的关系
func (this *AutoDoc)setTitleMap()  {
	for _,file:=range this.controllerDirFiles {
		this.parseControllerFiles(this.controllerDir+file)
	}

}

//获取接口标题
func (this *AutoDoc)getTitle() string  {
	return this.titleMap[this.url]
}

//载入controller下所有控制器文件
func (this *AutoDoc)loadControllerFiles() *AutoDoc {

	files, _ := ioutil.ReadDir(this.controllerDir)

	for _, f := range files {
		if strings.Contains(f.Name(), "_test.go") ||!strings.Contains(f.Name(),".go") {
			continue
		}


		this.controllerDirFiles=append(this.controllerDirFiles,f.Name())
	}
	return this
}

//从文件获取带有权限的方法
func (this *AutoDoc)parseControllerFiles(file string)  {

	content,err:=ioutil.ReadFile(file)
	if err != nil {
		fmt.Println(err)
		return
	}

	re:=regexp.MustCompile(fmt.Sprintf(`//([^\n]+)\nfunc \(this \*([A-Z][A-Z0-9a-z]*Controller)\) ([A-Z][A-Z0-9a-z]*)\(\) \{`))
	res:=re.FindAllStringSubmatch(string(content),-1)

	for _,v:=range res {
		if len(v)!= 4 {
			continue
		}
		name,_:= parseMethodNameAndPermTreeId(v[1])


		permUrl:= parsePermUrl(v[2],v[3])

		if name!="" && permUrl!= "" {
			this.titleMap[permUrl]=name
		}

	}

	return
}


//从注释中解析出方法名称和PermTreeId  测试数据:comment="获取登录用户信息{perm:22}"
func parseMethodNameAndPermTreeId(comment string)(name string,permTreeId uint32)  {
	if !strings.Contains(comment, "{perm") {
		name=comment
		return
	}

	comment=strings.TrimRight(comment,"}")
	commentArr:=strings.Split(comment,"{perm:")

	switch len(commentArr) {
	case 1:
		name=commentArr[0]
	case 2:

		name=commentArr[0]

		id,_:=strconv.Atoi(commentArr[1])
		permTreeId=uint32(id)


	}


	return
}

//从注释中解析出permUrl 测试数据: controller="UserController" action="AuthUser"
func parsePermUrl(controller string ,action string)(permUrl string)  {
	if controller=="" || action== "" {
		return
	}
	controller=strings.Replace(controller,"Controller","",1)


	permUrl=strings.ToLower(controller)+"/"+strings.ToLower(action)
	return
}


func inArray(item interface{},array interface{}) bool {
	if reflect.TypeOf(array).Kind() != reflect.Slice {
		return false
	}

	n := reflect.ValueOf(array).Len()
	for i := 0; i < n; i++ {
		if reflect.ValueOf(array).Index(i).Interface() == reflect.ValueOf(item).Interface() {
			return true
		}
	}

	return false

}

func isStruct(t reflect.Type) bool {
	return t.Kind() == reflect.Struct
}

//判定是否为结构体指针
func isStructPtr(t reflect.Type) bool {
	return t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct
}

//获取结构体或者指针的类型和值
func getStructTV(obj interface{}) (reflect.Type, reflect.Value, error) {
	objT := reflect.TypeOf(obj)
	objV := reflect.ValueOf(obj)

	switch {
	case isStruct(objT):
	case isStructPtr(objT):
		objT = objT.Elem()
		objV = objV.Elem()
	default:
		return objT, objV, fmt.Errorf("%v must be a struct or a struct pointer", obj)
	}

	return objT, objV, nil
}
